#!/usr/bin/env python
# -*- coding: utf-8 -*-

# @Author  : 张亦辰
# @Copyright (c) Copyright 2025 张亦辰，All rights reserved。
# @File    : win_main.py
# @Software: PyCharm
# @Time    : 2025/2/3 20:22

"""
程序说明：
主窗口程序，设置主窗口的事件绑定功能
"""
from PySide6.QtCore import (QRect, QSize, Qt)
from PySide6.QtGui import QMouseEvent, QIcon, QPixmap
from PySide6.QtWidgets import (QMainWindow, QLabel, QPushButton)
from loguru import logger

import board.board as board
import src.board.constants as const
from src.veiw.main_window import Ui_MainWindow
from src.win_about import AboutWindow
import src.veiw.res.imgs_rc


def delete_piece(piece: QPushButton):
    """
    删除棋子
    :param piece: 目标棋子
    :return:
    """
    piece.deleteLater()


class PieceButton(QPushButton):
    """
    自定义棋子类，可以扩展自定义属性
    """
    """
    棋子在列表中的索引
    """
    ind: int
    # 棋子的图片名称
    img: str

    def __init__(self, parent=None):
        super(PieceButton, self).__init__(parent)
        self.ind = None
        # 棋子的图片名称
        self.img = None


class StepLabel(QLabel):
    """
    自定义提示球，主要是来自定义单击事件
    """
    # 所在棋盘上的行和列
    r: int
    c: int
    # 所在列的左上角起始坐标
    sx: int
    sy: int

    def __init__(self, parent=None, parent_win=None):
        super(StepLabel, self).__init__(parent)
        # 所在棋盘上的行和列
        self.r = -1
        self.c = -1
        # 所在列的左上角起始坐标
        self.sx = -1
        self.sy = -1
        self.parentWin = parent_win

    def mousePressEvent(self, e: QMouseEvent):
        x = self.x()
        y = self.y()
        logger.debug(f"StepLabel->mousePressEvent x:{x} y:{y}")
        if self.parentWin is not None:
            self.parentWin.on_step_clicked(e, self)

    def set_board_pos(self, sx, sy, r, c):
        """
        设置该位置点的左上角坐标和行与列
        :param sx:
        :param sy:
        :param r:
        :param c:
        :return:
        """
        self.sx = sx
        self.sy = sy
        self.r = r
        self.c = c


def set_step_label(step: StepLabel, r: int, c: int):
    """
    把行和列，转换成坐标，
    :param step:
    :param r:
    :param c:
    :return:
    """
    woff = (board.SQUARE_SIZE - board.STEP_SIZE) >> 1
    hoff = (board.SQUARE_SIZE - board.STEP_SIZE) >> 1
    x, y = board.sq_pos(r, c)
    step.move(x + woff, y + hoff)
    step.set_board_pos(x, y, r, c)
    step.raise_()
    step.show()


class MainWindow(QMainWindow, Ui_MainWindow):
    # 当前该走棋的一方
    player: str
    # 电脑所执棋的一方
    ai_player: str
    # 上一次选择中的棋子，行棋成功后，则清空
    last_sel_piece: PieceButton
    # 棋盘类的实例
    board: board.Board
    # 盘面上的棋子
    pieceAry: list[PieceButton]
    # 待使用的提示球列表
    stepArr: list[StepLabel]
    # 正在棋盘上显示的提示球的列表
    stepArrVisible: list[StepLabel]

    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        self.init_ui()
        self.board = board.Board()
        # 校正棋盘位置
        board.set_board_pos(self.label_board.x(), self.label_board.y())
        # 默认为红方
        self.radio_red.click()

        # 盘面上的棋子
        self.pieceAry: list[PieceButton] = []
        # 初始化选择框
        self.tip_select_piece(-1, -1)
        # 初始化提示球
        self.stepArr: list[StepLabel] = self.init_step()
        # 正在显示的提示球的列表
        self.stepArrVisible: list[StepLabel] = []
        # 上一次选择中的棋子，行棋成功后，则清空
        self.last_sel_piece = None
        # 当前该走棋的一方
        self.player = None

    # 初始化窗口界面
    def init_ui(self):

        # 定义一系列的Action
        # 退出
        self.actionexit.setShortcut('Ctrl+Q')
        self.actionexit.setStatusTip('退出游戏')
        self.actionexit.triggered.connect(self.close)

        # 关于
        self.actionguanYu.triggered.connect(self.on_action_guan_yu_triggered)
        self.actionguanYu.setStatusTip('关于窗口')

        # 计算机为黑棋或红棋
        self.radio_black.clicked.connect(lambda: self.on_radio_clicked(const.playerBlack))
        self.radio_red.clicked.connect(lambda: self.on_radio_clicked(const.playerRed))

        # 开始
        self.pushButton_start.clicked.connect(self.start_board)
        # 结束
        self.pushButton_end.clicked.connect(self.clean_board)

        # 悔棋
        self.pushButton_back.clicked.connect(self.on_back_clicked)
        # 反转棋盘
        self.pushButton_reverse.clicked.connect(self.on_reverse_clicked)

    # 打开关于窗口
    def on_action_guan_yu_triggered(self):
        about = AboutWindow(self)  # 实例化子窗口
        about.exec()  # 显示子窗口

    def on_radio_clicked(self, player: str):
        """
        选择电脑的执棋的颜色
        :param player:
        :return:
        """
        self.ai_player = player
        logger.info("电脑的执棋的颜色：{}", player)

    def start_board(self):
        """
        开始棋局
        :return:
        """
        ind: int = 0
        self.clean_board()
        # fen 初始化
        squares = self.board.from_fen("rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1")

        # 普通初始化
        # squares = self.board.fromInit()
        for i in range(len(squares)):
            x, y = board.piece_pos(squares[i])
            piece = self.add_piece(ind, squares[i].imgKey, x, y, board.SQUARE_SIZE, board.SQUARE_SIZE)
            piece.ind = ind
            piece.imgKey = squares[i].imgKey
            piece.cnName = squares[i].cnName
            self.pieceAry.append(piece)
            ind += 1

        self.next_round()

    def clean_board(self):
        """
        清空棋盘
        :return:
        """
        # 清空棋盘上的棋子
        if self.pieceAry is not None:
            [delete_piece(piece) for piece in self.pieceAry]
            self.pieceAry = []

        # 清空棋盘上的提示球
        self.set_step_tip()
        # 清空棋盘上的选棋子的框
        self.tip_select_piece(-1, -1)
        # 清空上次选择的棋子
        self.last_sel_piece = None
        # 清空当前该走棋的一方
        self.player = None

    def on_reverse_clicked(self):
        """
        上下翻转棋盘
        :return:
        """
        # self.board.reverse()
        # self.start_board()
        logger.debug("上下翻转棋盘")
        pass

    def on_back_clicked(self):
        """
        悔棋
        :return:
        """
        # self.board.back()
        logger.debug("悔棋")
        pass

    def add_piece(self, name, img, x, y, width, height):
        """
        增加棋子测试增加一个棋子
        :param name: 棋子的名称
        :param img: 图片名称
        :param x: x位置
        :param y: y位置
        :param width: 棋子的宽度
        :param height: 棋子高度
        :return:
        """
        # push_button = QPushButton(self.centralwidget)
        push_button = PieceButton()
        push_button.setObjectName("button_piece_" + str(name))
        push_button.setParent(self.centralwidget)
        push_button.ind = name
        push_button.img = img
        push_button.setGeometry(QRect(x, y, width, height))
        push_button.setStyleSheet(u"background-color: transparent;")

        icon4 = QIcon()
        icon4.addFile(f":/s1/imgs/{board.IMG_DIR}/{img}.gif", QSize(),
                      QIcon.Mode.Normal, QIcon.State.Off)
        push_button.setIcon(icon4)
        push_button.setIconSize(QSize(width, height))
        push_button.clicked.connect(lambda: self.on_piece_clicked(push_button))
        push_button.show()
        return push_button

    def on_piece_clicked(self, button: PieceButton):
        """
        棋子点击事件，主要考虑三件事：
        1.标识所走的步骤
        2.标识当前的子可以走的位置
        3、试着走棋，如果成功了，则标识为两个位置
        :param button:
        :return:
        """
        logger.info("棋子点击了：{}，{}", str(button.objectName()), button.img)
        # 获取目标棋子的颜色
        piece_player = button.img[:1]
        # 1.走棋的第一步，即选定源棋子时
        if self.last_sel_piece is None:
            # 1.1.如果不是点击该走棋一方的棋子，则不做任何处理
            if self.player != piece_player:
                return
            # 1.2. 标识选中的位置
            self.tip_select_piece(button.x(), -1, button.y())
            # 1.3. 计算可走位置提示
            self.set_step_tip(button)
            # 1.4. 记录当前棋子为源棋子
            self.last_sel_piece = button
        else:
            # 2.走棋的第二步，即点击目标棋子时
            # 2.1.点击了相同的棋子，侧什么也不用做
            if button == self.last_sel_piece:
                logger.debug("点击了相同的棋子{}", button)
                return
            # 2.2. 当前棋子是否为该走方的，如果是，则为更新源棋子
            if self.player == piece_player:
                # 2.2.1. 标识选中的位置
                self.tip_select_piece(button.x(), -1, button.y())
                # 2.2.2. 计算可走位置并提示
                self.set_step_tip(button)
                # 2.2.3. 记录当前棋子为源棋子
                self.last_sel_piece = button
            else:
                # 2.3. 点击了对方棋子，如果可以走，则是吃子走棋，否则转2.4
                # 2.3.1.获取当前棋子所在的行和列
                c, r = self.board.get_piece_rc(button.ind)
                # 2.3.2.试走棋
                move_flag = self.move_piece(button.x(), button.y(), c, r, button)
                if move_flag:
                    # 2.3.3. 标识棋子选中和源棋子位置
                    self.tip_select_piece(self.last_sel_piece.x(), button.x(),
                                          self.last_sel_piece.y(), button.y())
                    # todo 2.3.4.播放走棋提示音，可以提示吃子，或者胜利（吃掉了老将）

                    # 清空步骤提示
                    self.set_step_tip()
                    # 2.3.5. 准备下一回合
                    self.last_sel_piece = None
                    self.next_round()

                # 2.4 如果不符合规则，不能走，则什么也不做

    def mousePressEvent(self, event: QMouseEvent):
        """
        单击事件，只处理单击在棋盘上的事件，其他的不管
        :param event:
        :return:
        """
        x = event.pos().x()
        y = event.pos().y()
        # 棋盘的左上角坐标x1和y1
        x1 = self.centralWidget().x() + board.SQUARE_LEFT
        y1 = self.centralWidget().y() + board.SQUARE_TOP
        x2 = x1 + board.SQUARE_SIZE * 9
        y2 = y1 + board.SQUARE_SIZE * 10
        # 转换为相对中央区的坐标
        xc = x - self.centralWidget().x()
        yc = y - self.centralWidget().y()
        # logger.info('点击位置x={},y={}', x, y)
        # logger.info('棋盘位置x={},x1={},y={},y1={}', x1, x2, y1, y2)
        if (x1 <= x <= x2) and (y1 <= y <= y2):
            if event.button() == Qt.MouseButton.LeftButton:
                # 获取点击在棋盘上的行和列，该行和列对应的坐标
                r, c, px, py = board.get_click_pos(xc, yc)
                logger.debug("点击的棋盘上的行列为: {} X {}, 像素为：{} X {}", r, c, px, py)

                # 是否可以走该步
                if self.last_sel_piece is not None:
                    # 在移动前棋子前，先记录原棋子坐标，用以标识位置
                    sx, sy = self.last_sel_piece.x(), self.last_sel_piece.y()
                    move_flag = self.move_piece(px, py, c, r)
                    if move_flag:
                        # 标识源和目标位置
                        self.tip_select_piece(sx, px, sy, py)
                        # 清空步骤提示
                        self.set_step_tip()
                        # 播放走棋提示音 todo

                        # 准备下一回合
                        self.last_sel_piece = None
                        self.next_round()
                    else:
                        # 播放非法走棋的提示音 todo
                        logger.info("非法走棋，不可移动棋子")

    def tip_select_piece(self, x1, x2, y1=-1, y2=-1):
        """
        设置选中的棋子的提示框
        :param x1:
        :param y1:
        :param x2:
        :param y2:
        :return:
        """

        # 当位置为-1时，表示需要隐藏
        def __set_label(label, x, y):
            if x == -1:
                label.hide()
            else:
                label.move(x, y)
                label.show()

        __set_label(self.label_checked_s, x1, y1)
        __set_label(self.label_checked_t, x2, y2)

    def init_step(self):
        """
        初始化步骤提示图标，主要是生成数组，并隐藏初始状态
        :return:
        """
        steps: list = []
        for i in range(0, 19):
            step = StepLabel(self.centralwidget, self)
            step.setObjectName(f"label_step_{i}")
            step.setGeometry(QRect(0, 0, board.STEP_SIZE, board.STEP_SIZE))
            step.setPixmap(QPixmap(f":/s1/imgs/{board.IMG_DIR}/step.png"))
            step.raise_()
            step.hide()
            steps.append(step)
        return steps

    def on_step_clicked(self, e: QMouseEvent, step):
        """
        点击了步骤提示球后的处理
        :param e: 事件对像
        :param step: 步骤球
        :return:
        """
        logger.debug("点击了提示球x={},y={},r={},c={},sx={},sy={}", step.x(), step.y(),
                     step.r, step.c, step.sx, step.sy)
        # 判断当前位置是否有棋子 如果有，则执行棋子的点击框，否则点击棋盘
        pieces = list(filter(lambda cm: cm.x() == step.sx and cm.y() == step.sy, self.pieceAry))
        if len(pieces) > 0:
            self.on_piece_clicked(pieces[0])
        else:
            # 忽略事件处理，接着向父组件传递，即点击棋盘
            e.ignore()

    def move_piece(self, x, y, c: int, r: int, piece: PieceButton = None):
        """
        走棋方法
        :param x: 目标区域的横坐标
        :param y: 目标区域的纵坐标
        :param c: 目标列
        :param r: 目标行
        :param piece: 目标棋子
        :return:
        """
        if piece is not None:
            lc, lr = self.board.get_piece_rc(int(piece.ind))
            m_flag = self.board.move(self.last_sel_piece.ind, lc, lr, int(piece.ind))
            if m_flag:
                # 删除目标棋子
                self.pieceAry.remove(piece)
                delete_piece(piece)
        else:
            m_flag = self.board.move(self.last_sel_piece.ind, c, r)

        # 移动原棋子，到目标位置
        if m_flag:
            self.last_sel_piece.move(x, y)
        return m_flag

    def next_round(self):
        """
        下一回合操作，即标识下一步该走的一方（红或黑）
        :return:
        """
        # 1.清空提示的步骤点
        self.set_step_tip()
        # 2.交换行棋方
        self.player = const.playerBlack if self.player == const.playerRed else const.playerRed

    def set_step_tip(self, button: PieceButton = None):
        """
        根据传入的 button 参数来设置或清空步骤提示球。
        如果 button 参数不为空，则根据该棋子的索引获取下一步可走的位置，
        并在这些位置显示提示球；如果 button 参数为空，则清空之前显示的所有提示球。
        :param button: 当前棋子，为空时则清空提示
        :return:
        """
        # 初始化一个空列表，用于存储下一步可走的位置
        arr: list[(int, int)] = []
        # 如果传入了 button 参数
        if button is not None:
            # 调用 board 对象的 get_next_steps_tip 方法，根据 button 的索引获取下一步可走的位置
            arr = self.board.get_next_steps_tip(button.ind)

        # 先清空之前的显示
        # 如果 stepArrVisible 列表中有元素
        if len(self.stepArrVisible) > 0:
            # 遍历 stepArrVisible 列表中的每个提示球,并隐藏
            [step.hide() for step in self.stepArrVisible]
            self.stepArrVisible = []

        # 提示最大步数
        if arr is not None:
            ind = len(self.stepArr)
            for i in range(len(arr)):
                if i < ind:
                    sp = self.stepArr[i]
                    set_step_label(sp, arr[i][0], arr[i][1])
                    self.stepArrVisible.append(sp)
